#include "vsf.h"
#include "vsfav_usb.h"

#include "component/usb/class/common/UAC/vsfusb_UAC.h"

static uint8_t *vsfav_usb_get_unit_desc(uint8_t *desc, uint32_t desc_len,
		uint8_t id, uint8_t type)
{
	struct uavc_unit_descriptor_header *ud;

	while (desc_len > 0)
	{
		if (vsfusbh_get_extra_descriptor(desc, desc_len, USB_DT_CS_INTERFACE, (void **)&ud))
			return NULL;

		desc_len -= (uint8_t *)ud - desc;
		desc_len -= ud->header.bLength;
		desc = (uint8_t *)ud + ud->header.bLength;

		if ((!id && (ud->header.bDescriptorSubType == type)) ||
			(id && (ud->header.bDescriptorSubType >= UAC_INPUT_TERMINAL) &&
			 	(ud->header.bDescriptorSubType <= UAC1_EXTENSION_UNIT) &&
				(ud->id == id)))
			return (uint8_t *)ud;
	}
	return NULL;
}

void *vsfav_usb_getbuf(struct vsfav_usb_unit_parser_t *parser, uint32_t size)
{
	void *result = parser->buf;
	if (result != NULL)
	{
		memset(result, 0, size);
		parser->buf += size;
	}
	parser->size += size;
	return result;
}

static void vsfav_usb_increase_unit_number(struct vsfav_usb_unit_parser_t *parser)
{
	parser->unit_number++;
	if (parser->av_dev != NULL)
		parser->av_dev->topology.num_of_units++;
}

static vsf_err_t vsfav_usb_parse_unit_bydesc(struct vsfav_usb_unit_parser_t *parser,
		struct vsfav_unit_t *master, struct uavc_unit_descriptor_header *ud);
static vsf_err_t vsfav_usb_parse_unit_byid(struct vsfav_usb_unit_parser_t *parser,
		struct vsfav_unit_t *master, uint8_t id)
{
	if (mskarr_get(parser->idmask, id - 1))
	{
		if (parser->av_dev != NULL)
		{
			struct vsfav_unit_t *unit = vsfav_get_unit_byid(parser->av_dev, id);
			vsfav_unit_add_input(master, unit);
		}
		return VSFERR_NONE;
	}
	else
	{
		struct uavc_unit_descriptor_header *ud = (struct uavc_unit_descriptor_header *)\
				vsfav_usb_get_unit_desc(parser->desc, parser->desc_len, id, 0);
		return ud ? vsfav_usb_parse_unit_bydesc(parser, master, ud) : VSFERR_FAIL;
	}
}

static vsf_err_t vsfav_usb_parse_unit_bydesc(struct vsfav_usb_unit_parser_t *parser,
		struct vsfav_unit_t *master, struct uavc_unit_descriptor_header *ud)
{
	struct vsfav_dev_t *av_dev = parser->av_dev;
	struct vsfav_unit_t *unit;
	struct vsfav_input_t *input;

	mskarr_set(parser->idmask, ud->id - 1);
	unit = (struct vsfav_unit_t *)vsfav_usb_getbuf(parser, sizeof(*unit));
	if (parser->op.parse_desc(parser, unit, ud))
		return VSFERR_FAIL;
	if (unit && av_dev)
	{
		if ((unit->type == VSFAV_INPUT) || (unit->type == VSFAV_STREAM_INPUT))
		{
			unit->next = av_dev->topology.input_units;
			av_dev->topology.input_units = unit;
			av_dev->topology.num_of_inputs++;
		}
		else if ((unit->type == VSFAV_OUTPUT) || (unit->type == VSFAV_STREAM_OUTPUT))
		{
			unit->next = av_dev->topology.output_units;
			av_dev->topology.output_units = unit;
			av_dev->topology.num_of_outputs++;
		}
		av_dev->topology.units[parser->unit_number] = unit;
	}
	vsfav_usb_increase_unit_number(parser);

	if (master) vsfav_unit_add_input(master, unit);
	if (parser->unit.input_number)
	{
		uint8_t input_number = parser->unit.input_number;
		if (input = (struct vsfav_input_t *)vsfav_usb_getbuf(parser, sizeof(*input) + sizeof(unit) * input_number))
		{
			unit->input = input;
			input->number = input_number;
		}

		for (uint8_t i = 0; i < input_number; i++)
			if (vsfav_usb_parse_unit_byid(parser, unit, parser->unit.input_IDs[i]))
				return VSFERR_FAIL;
	}
	return VSFERR_NONE;
}

vsf_err_t vsfav_usb_generate_topology(struct vsfav_usb_unit_parser_t *parser)
{
	struct uac1_ac_header_descriptor *uac_header;
	struct uavc_unit_descriptor_header *ud;
	uint32_t tmplen;
	uint8_t *ptr;

	uac_header = (struct uac1_ac_header_descriptor *)vsfav_usb_get_unit_desc(
			parser->desc, parser->desc_len, 0, UAC_HEADER);
	if (!uac_header) return VSFERR_FAIL;

	tmplen = parser->desc_len;
	tmplen -= (uint8_t *)uac_header - parser->desc;
	ptr = (uint8_t *)uac_header;
	while (tmplen > 0)
	{
		ud = (struct uavc_unit_descriptor_header *)vsfav_usb_get_unit_desc(
				ptr, tmplen, 0, UAC_OUTPUT_TERMINAL);
		if (!ud) break;

		tmplen -= (uint8_t *)ud - (uint8_t *)ptr;
		tmplen -= ud->header.bLength;
		ptr = (uint8_t *)ud + ud->header.bLength;

		if (vsfav_usb_parse_unit_bydesc(parser, NULL, ud))
			return VSFERR_FAIL;
	}
	return VSFERR_NONE;
}

void vsfav_usb_parser_init(struct vsfav_usb_unit_parser_t *parser)
{
	parser->unit_number = 0;
	memset(parser->idmask, 0, sizeof(parser->idmask));
}
